package com.wujunshen.mongodb;

import static java.util.concurrent.TimeUnit.MILLISECONDS;

import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.connection.ConnectionPoolSettings;
import com.mongodb.connection.ServerSettings;
import com.mongodb.connection.SocketSettings;
import com.mongodb.connection.SslSettings;
import com.wujunshen.mongodb.properties.MongoGroup;
import com.wujunshen.mongodb.properties.MongoGroups;
import java.util.NoSuchElementException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoClientDbFactory;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
import org.springframework.lang.NonNull;
import org.springframework.util.StringUtils;

/**
 * @author frank woo(吴峻申) <br>
 * email:<a href="mailto:frank_wjs@hotmail.com">frank_wjs@hotmail.com</a> <br>
 * @date 2020/2/7 5:33 下午 <br>
 */
@Slf4j
@Configuration
public class MongoAutoConfiguration implements BeanFactoryPostProcessor, EnvironmentAware {
    @Value("${spring.application.name}")
    private String applicationName;
    
    private Binder binder;
    
    @Override
    public void postProcessBeanFactory(@NonNull ConfigurableListableBeanFactory beanFactory)
            throws BeansException {
        MongoGroups mongoGroups;
        // 获取所有自定义的配置
        try {
            mongoGroups = binder.bind("spring.data.mongodb", MongoGroups.class).get();
        } catch (NoSuchElementException e) {
            log.error("\n没有配置mongodb自定义属性! 描述:{}\n", ExceptionUtils.getStackTrace(e));
            return;
        }
        
        log.info(mongoGroups.getGroups().toString());
        for (MongoGroup group : mongoGroups.getGroups()) {
            String host = group.getHost();
            int port = group.getPort();
            ConnectionString connectionString = new ConnectionString("mongodb://" + host + ":" + port);
            
            log.info("Creating Mongo client for: {}:{}", host, port);
            
            MongoClientSettings mongoClientSettings =
                    MongoClientSettings.builder()
                            .applicationName(applicationName)
                            .applyToServerSettings(
                                    builder ->
                                            ServerSettings.builder()
                                                    .applyConnectionString(connectionString)
                                                    .heartbeatFrequency(group.getHeartbeatFrequency(), MILLISECONDS)
                                                    .minHeartbeatFrequency(group.getMinHeartbeatFrequency(),
                                                            MILLISECONDS))
                            .applyToConnectionPoolSettings(
                                    builder ->
                                            ConnectionPoolSettings.builder()
                                                    .applyConnectionString(connectionString)
                                                    .maxWaitTime(group.getMaxWaitTime(), MILLISECONDS)
                                                    .maxConnectionIdleTime(group.getMaxConnectionIdleTime(),
                                                            MILLISECONDS)
                                                    .maxConnectionLifeTime(group.getMaxConnectionLifeTime(),
                                                            MILLISECONDS)
                                                    .minSize(group.getMinConnectionsPerHost())
                                                    .maxSize(group.getMaxConnectionsPerHost()))
                            .applyToSocketSettings(
                                    builder ->
                                            SocketSettings.builder()
                                                    .applyConnectionString(connectionString)
                                                    .connectTimeout(group.getConnectTimeout(), MILLISECONDS)
                                                    .readTimeout(group.getSocketTimeout(), MILLISECONDS))
                            .applyToSslSettings(
                                    builder ->
                                            SslSettings.builder()
                                                    .applyConnectionString(connectionString)
                                                    .enabled(group.isSslEnabled())
                                                    .invalidHostNameAllowed(group.isSslInvalidHostNameAllowed()))
                            .build();
            
            MongoClient mongoClient = MongoClients.create(mongoClientSettings);
            
            SimpleMongoClientDbFactory mongoDbFactory =
                    new SimpleMongoClientDbFactory(mongoClient, group.getDatabase());
            
            MappingMongoConverter converter = buildConverter(mongoDbFactory, group.isShowClass());
            
            // 注册mongoTemplate
            registryTemplate(
                    beanFactory, group.getMongoTemplateName(), new MongoTemplate(mongoDbFactory, converter));
            
            // 如果属性gridFsTemplateName有值,则注册gridFsTemplate
            if (StringUtils.hasText(group.getGridFsTemplateName())) {
                registryTemplate(
                        beanFactory,
                        group.getGridFsTemplateName(),
                        new GridFsTemplate(mongoDbFactory, converter));
            }
        }
    }
    
    /**
     * 注册 mongoTemplate
     */
    private void registryTemplate(
            ConfigurableListableBeanFactory beanFactory, String beanName, Object templateBean) {
        beanFactory.registerSingleton(beanName, templateBean);
        beanFactory.applyBeanPostProcessorsAfterInitialization(templateBean, beanName);
        log.info("\n{} has bean loaded\n", beanName);
    }
    
    private MappingMongoConverter buildConverter(
            SimpleMongoClientDbFactory mongoDbFactory, boolean showClass) {
        MappingMongoConverter converter =
                new MappingMongoConverter(
                        new DefaultDbRefResolver(mongoDbFactory), new MongoMappingContext());
        if (!showClass) {
            converter.setTypeMapper(new DefaultMongoTypeMapper(null));
        }
        return converter;
    }
    
    @Override
    public void setEnvironment(@NonNull Environment environment) {
        binder = Binder.get(environment);
    }
}
